# coding: utf8


import time
import functools


def printtime1(fun):
    def wrapper():
        start = time.time()
        result = fun()
        print('time = {:.4f}'.format(time.time()-start))
        return result
    return wrapper


def printtime2(fun):
    def wrapper(m=1000):
        start = time.time()
        result = fun(n=m)
        print('time = {:.4f}'.format(time.time()-start))
        return result
    return wrapper

def printtime3(hint='time'):
    def deco(fun):
        def wrapper(*args, **kwargs):
            start = time.time()
            result = fun(*args, **kwargs)
            print("{} = {}".format(hint, time.time()-start))
            return result
        return wrapper
    return deco


def printtime4(fun):
    @functools.wraps(fun)
    def wrapper():
        start = time.time()
        result = fun()
        print('time = {:.4f}'.format(time.time()-start))
        return result
    return wrapper


class printtime_class(object):

    def __init__(self):
        self.hint = 'time'

    def __call__(self, fun):
        def dec():
            start = time.time()
            result = fun()
            print('{} = {:.4f}'.format(self.hint, time.time() - start))
            return result
        return dec


class printtime_class_with_params:

    def __init__(self, hint='time'):
        self.hint = hint

    def __call__(self, fun):
        def dec(*args, **kwargs):
            start = time.time()
            result = fun(*args, **kwargs)
            print('{} = {:.4f}'.format(self.hint, time.time() - start))
            return result
        return dec


def fun1_source(n=100000):
    """计算平方和"""
    return sum([x**2 for x in range(n)])


@printtime1
def fun1(n=100000):
    """计算平方和"""
    return sum([x**2 for x in range(n)])


@printtime2
def fun2(n=100000):
    return sum([x**2 for x in range(n)])


@printtime3(hint='consume')
def fun3(n=100000):
    return sum([x**2 for x in range(n)])


@printtime_class()
def fun4(n=100000):
    return sum([x**2 for x in range(n)])


@printtime_class_with_params(hint="use class dec, time")
def fun5(n=100000):
    return sum([x**2 for x in range(n)])


class TestPrinttimeClass2:

    @printtime_class_with_params('used in class method, time')
    def test6(self, n=100000):
        return sum([x**2 for x in range(n)])


@printtime4
def fun7(n=100000):
    """计算平方和"""
    return sum([x**2 for x in range(n)])


def test7_name_doc_after_decorated():
    print('test1.__name__ = {}'.format(fun1.__name__))
    print('test1.__doc__ = {}'.format(fun1.__doc__))
    help(fun1)

    print('test7.__name__ = {}'.format(fun7.__name__))
    print('test7.__doc__ = {}'.format(fun7.__doc__))
    help(fun7)


def dec1(fun):
    x = 10

    def wrapper():
        return fun() + x

    return wrapper


def dec2(fun):
    y = 20

    def wrapper():
        return fun() * y

    return wrapper


@dec1
@dec2
def fun8(n=0):
    return n


@dec2
@dec1
def fun9(n=0):
    return n


def test_deco_overlay():
    """
    # dec1(dec2(test8(n))) = (n * 20) + 10 = 610
    >>> fun8()
    10

    # dec2(dec1(test9(n))) = (n + 10) * 20 = 800

    >>> fun9()
    200
    """


# deco1 fun for decorate class
def fun_dec_class(cls):
    cls.x = 1000
    return cls


@fun_dec_class
class Class1:
    x = 100

    @classmethod
    def m1(cls, z):
        return z + cls.x


def test_deco_class1():
    mc = Class1()
    print(mc.m1(100))


# deco2 function for decorate class
def fun_dec_class2(cls):
    a = 100

    def wrapper(y=10):
        # add some to cls
        # modify class attribute
        cls.x = y
        # add class method
        cls.m2 = lambda v: v + a
        return cls

    return wrapper


@fun_dec_class2
class Class2:
    x = 100

    def __int__(self, y=100):
        self.y = y

    @classmethod
    def m1(cls, z):
        return z + cls.x


def test_deco_class2():
    mc = Class2(10)
    print(mc.m1(100))
    print(mc.m2(100))


# decorator for object method
def dec_object_method(fun):
    def wrapper(obj, *args, **kwargs):
        result = fun(obj, *args, **kwargs)**2
        return result
    return wrapper


class Class3:
    @dec_object_method
    def m(self, x):
        return x**2


def test_dec_object_method():
    dc = Class3()
    print('DecoClass method m(3) = {}'.format(dc.m(3)))


# deco fun for decorate class method
class Class4:

    @classmethod
    @dec_object_method
    def m1(cls, x):
        return x ** 2

    @staticmethod
    @dec_object_method
    def m2(x):
        return x ** 2


def test_dec_class_method():
    print('DecoClass method m1(3) = {}'.format(Class4.m1(3)))
    print('DecoClass method m2(3) = {}'.format(Class4.m2(3)))


class ClassDecoFun:

    def __init__(self):
        self.hint = 'time'

    def __call__(self, fun):
        def dec():
            start = time.time()
            result = fun()
            print('{} = {:.4f}'.format(self.hint, time.time() - start))
            return result
        return dec


class ClassDecoClass:
    def __init__(self, cls):
        self._cls = cls

    def __call__(self):
        # modify _cls
        self._cls.x = 1000
        self._cls.y = 2000

        @classmethod
        def add_method(cls):
            return cls.x + cls.y

        self._cls.add_method = add_method

        return self._cls


class ClassDecoClassWithParams:
    pass


@ClassDecoClass
class Class5:
    x = 100

    def __init__(self, z=None):
        self.z = z


def test_class_deco_class():
    # 生成新的类
    new_class5 = Class5()
    print('new_class5.x = {}, new_class5.y = {}'.format(new_class5.x, new_class5.y))
    print('new_class5.add_method() = {}'.format(new_class5.add_method()))

    # 生成对象
    obj5 = new_class5(5000)
    print(obj5.z)
    print(obj5.add_method())
